package cn.com.greatwall.system.modules.oauth2.config;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import cn.com.greatwall.common.annotation.AnonymousAccess;
import cn.com.greatwall.common.utils.enums.RequestMethodEnum;
import cn.com.greatwall.system.modules.security.security.JwtAccessDeniedHandler;
import cn.com.greatwall.system.modules.security.security.JwtAuthenticationEntryPoint;
import lombok.RequiredArgsConstructor;

/**
 * @Author herw
 * @Time 2020-12-09 19:16:31
 * @Version 1.0
 * @Description: TODO(用一句话描述该文件做什么)
 */
@Configuration
@RequiredArgsConstructor
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    private static final String DEMO_RESOURCE_ID = "greatwall";
    private final ApplicationContext applicationContext;
    private final CorsFilter corsFilter;
    private final JwtAuthenticationEntryPoint authenticationErrorHandler;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
    private final TokenStore tokenStore;
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.tokenStore(tokenStore).resourceId(DEMO_RESOURCE_ID);//.stateless(false);
    }
    
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        // 搜寻匿名标记 url： @AnonymousAccess
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = ((RequestMappingHandlerMapping) applicationContext
                .getBean("requestMappingHandlerMapping")).getHandlerMethods();
        // 获取匿名标记
        Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
        httpSecurity.csrf().ignoringAntMatchers("/oauth/authorize", "/oauth/token", "/oauth/rest_token");
        httpSecurity
                // 禁用 CSRF
                .csrf().disable().addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
                // 授权异常
                .exceptionHandling().authenticationEntryPoint(authenticationErrorHandler)
                .accessDeniedHandler(jwtAccessDeniedHandler)
                // 防止iframe 造成跨域
                .and().headers().frameOptions().disable()
                // 不创建会话
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                // 静态资源等等
                .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/webSocket/**")
                .permitAll()
                // swagger 文档
                .antMatchers("/swagger-ui.html").permitAll().antMatchers("/swagger-resources/**").permitAll()
                .antMatchers("/webjars/**").permitAll().antMatchers("/*/api-docs").permitAll()
                // 文件
                .antMatchers("/avatar/**").permitAll().antMatchers("/file/**").permitAll()
                // 阿里巴巴 druid
                .antMatchers("/druid/**").permitAll()
                // Oauth2.0
                .antMatchers("/oauth/**","/authentication/base", "/auth/login").permitAll()
                // 放行OPTIONS请求
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                // 自定义匿名访问所有url放行：允许匿名和带Token访问，细腻化到每个 Request 类型
                // GET
                .antMatchers(HttpMethod.GET,
                        anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0]))
                .permitAll()
                // POST
                .antMatchers(HttpMethod.POST,
                        anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0]))
                .permitAll()
                // PUT
                .antMatchers(HttpMethod.PUT,
                        anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0]))
                .permitAll()
                // PATCH
                .antMatchers(HttpMethod.PATCH,
                        anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0]))
                .permitAll()
                // DELETE
                .antMatchers(HttpMethod.DELETE,
                        anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0]))
                .permitAll()
                // 所有类型的接口都放行
                .antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
                // 所有请求都需要认证
                .anyRequest().authenticated();
    }

    private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
        Map<String, Set<String>> anonymousUrls = new HashMap<>(6);
        Set<String> get = new HashSet<>();
        Set<String> post = new HashSet<>();
        Set<String> put = new HashSet<>();
        Set<String> patch = new HashSet<>();
        Set<String> delete = new HashSet<>();
        Set<String> all = new HashSet<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
            if (null != anonymousAccess) {
                List<RequestMethod> requestMethods = new ArrayList<>(
                        infoEntry.getKey().getMethodsCondition().getMethods());
                RequestMethodEnum request = RequestMethodEnum
                        .find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType()
                                : requestMethods.get(0).name());
                switch (Objects.requireNonNull(request)) {
                case GET:
                    get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                    break;
                case POST:
                    post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                    break;
                case PUT:
                    put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                    break;
                case PATCH:
                    patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                    break;
                case DELETE:
                    delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                    break;
                default:
                    all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
                    break;
                }
            }
        }
        anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
        anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
        anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
        anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
        anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
        anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
        return anonymousUrls;
    }
}
